﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;

using Tamir.SharpSsh;
using Krystalware.UploadHelper;
using VistaControls.TaskDialog;

namespace PowerWaveAddin
{
    /// <summary>
    /// This class manages the uploading of a presentation.  While a presentation is being
    /// uploaded, the Addin GUI will be dissabled and a 
    /// </summary>
    class LegacyUploader
    {
        #region Fields and Properties
        private PowerWaveRibbon.ControlToggler toggleGui;

        private PublishStatusAeroForm statusForm;
        private BackgroundWorker worker;

        public PublishStatusAeroForm StatusForm
        {
            get { return statusForm; }
            set { statusForm = value; }
        } 
        #endregion

        public LegacyUploader(PowerWaveRibbon.ControlToggler toggleGui)
        {
            this.toggleGui = toggleGui;
            this.worker = new System.ComponentModel.BackgroundWorker();
            this.worker.WorkerReportsProgress = true;
            this.worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            this.worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);

            this.statusForm = new PublishStatusAeroForm();
        }

        #region Main Thread Methods
        public void StartScpUpload(string pName, string email, string dirName, StorageConfiguration config)
        {
            Scp scp = new Scp(config.Host, config.UserName, config.Password);
            scp.OnTransferProgress += new FileTransferEvent(scp_OnTransferProgress);

            string targetDir = config.Directory + "/" + dirName;

            DirectoryInfo di = new DirectoryInfo(dirName);
            FileInfo[] files = di.GetFiles("Slide*.png", SearchOption.TopDirectoryOnly);
            try
            {
                if (!scp.Connected)
                    scp.Connect(config.Port);
                scp.Mkdir(config.Directory);
                scp.Mkdir(targetDir);

                foreach (FileInfo file in files)
                {
                    try
                    {
                        scp.Put(file.FullName, targetDir + "/");
                    }
                    catch (Exception e)
                    {
                        Aero.DisplayErrorMessage(
                            "Unable to upload slide",
                            "Could not upload slide" + file.Name + " to the remote host.",
                            e.ToString());
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                Aero.DisplayErrorMessage(
                    "Unable to upload to remote host",
                    "Could not upload slides to the specified server:",
                    e.ToString());
            }
            finally
            {
                if (scp.Connected)
                    scp.Close();
                this.toggleGui(true);
            }
        }

        void scp_OnTransferProgress(string src, string dst, int transferredBytes, int totalBytes, string message)
        {
            return;
        }

        public void StartUpload(string pName, string email, string dirName)
        {
            NameValueCollection formFields = new NameValueCollection(3);
            formFields[FieldNames.NewPresentationId] = pName;
            formFields[FieldNames.EmailAddress] = email;
            formFields[FieldNames.Password] = String.Empty;

            FileInfo[] files = null;
            try
            {
                // get the list of slide files to upload
                DirectoryInfo di = new DirectoryInfo(dirName);
                files = di.GetFiles("Slide*.png", SearchOption.TopDirectoryOnly);

                this.statusForm.AppendStringToReportDelegateAccessor(
                        Properties.Resources.SlideOrderMessage,
                        string.Empty);
                
                Array.Sort(files, new FileInfoLogicalComparer());

                this.statusForm.UpdateLastEventStatusDelegateAccessor(Properties.Resources.Done);
            }
            catch (FileNotFoundException ex)
            {
                this.statusForm.PopUpError(ex.ToString());
                ThisAddIn.currentUploadState = UploadCode.FAIL;
                this.toggleGui(true);
            }

            UploadParams uParams = new UploadParams()
            {
                Email = email,
                Files = files,
                NewName = dirName
            };

            this.worker.RunWorkerAsync(uParams);

        }

        public void ShowStatusForm()
        {
            this.statusForm.Show();
        }
        #endregion

        #region Background Worker Methods
        /// <summary>
        /// Asynchronous metho called by the background worker.  This runs in a background thread
        /// and is the method that makes all the web service calls.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            UploadParams p = e.Argument as UploadParams;
            if (p == null)
            {
                e.Result = new ArgumentNullException();
                return;
            }

            FileInfo[] files = p.Files;
            double fileCount = (double)files.Length;
            if (fileCount == 0)
            {
                return;
            }

            IList<UploadFile[]> uploadBatches = GroupSlidesIntoBatches(files);

            string response;
            try
            {
                double slidesUploaded = 0.0;
                this.statusForm.Invoke(
                    this.statusForm.AppendStringToReportDelegateAccessor,
                        Properties.Resources.UploadingImagesMessage,
                        string.Empty);

                // Create the presentation using the first batch of slides
                NameValueCollection creationFields = new NameValueCollection();
                creationFields.Add(FieldNames.NewPresentationId, p.NewName);
                creationFields.Add(FieldNames.EmailAddress, p.Email);

                response = HttpUploadHelper.Upload(UrlStrings.PresentationServiceUrl, uploadBatches[0], creationFields);
                slidesUploaded += (double)uploadBatches[0].Length;
                bw.ReportProgress((int)(100 * slidesUploaded / fileCount));

                Globals.ThisAddIn.presentationManager.ChangeCurrentPresentationKey(response);

                // TODO: Add handling of responses other than the key (such as errors)

                // Add remaining slide batches
                NameValueCollection additionFields = new NameValueCollection();
                additionFields.Add(FieldNames.ExistingPresentationKey, response);
                for (int i = 1; i < uploadBatches.Count; i++)
                {
                    string resp = HttpUploadHelper.Upload(UrlStrings.SlideServiceUrl, uploadBatches[i], additionFields);
                    slidesUploaded += (double)uploadBatches[i].Length;
                    bw.ReportProgress((int)(100 * slidesUploaded / fileCount));
                }

            }
            catch (WebException ex)
            {
                e.Result = ex;
            }
            catch (IOException ex)
            {
                e.Result = ex;
            }
            finally
            {
                DisposeUploadFileStreams(uploadBatches);
                DeleteFiles(files);
            }
        }

        /// <summary>
        /// Executes after DoWork has successfully completed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            bool keepWindowOpen = false;
            if (e.Error != null)
            {
                this.statusForm.Invoke(
                    this.statusForm.UpdateLastEventStatusDelegateAccessor,
                    Properties.Resources.Failed);
                this.statusForm.Invoke(
                    this.statusForm.PopErrorMessageDelegateAccessor,
                        e.Error.ToString());
                keepWindowOpen = true;
                ThisAddIn.currentUploadState = UploadCode.FAIL;
            }
            else if (e.Result is Exception)
            {
                this.statusForm.Invoke(
                    this.statusForm.UpdateLastEventStatusDelegateAccessor,
                    Properties.Resources.Failed);
                this.statusForm.Invoke(
                    this.statusForm.PopErrorMessageDelegateAccessor,
                        ((Exception)(e.Result)).ToString());
                keepWindowOpen = true;
                ThisAddIn.currentUploadState = UploadCode.FAIL;
            }
            else
            {
                this.statusForm.Invoke(
                        this.statusForm.UpdateLastEventStatusDelegateAccessor,
                        Properties.Resources.Success);
                ThisAddIn.currentUploadState = UploadCode.SUCCESS;
            }
            this.toggleGui(true);
            this.statusForm.Invoke(this.statusForm.ProgressBarDelegateAccessor, 0);
            if (!keepWindowOpen)
                this.statusForm.Close();
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.statusForm.Invoke(this.statusForm.ProgressBarDelegateAccessor, e.ProgressPercentage);
        }
        #endregion

        #region Auxiliary Methods
        /// <summary>
        /// Makes sure that all UploadFile inststances have closed their streams.
        /// </summary>
        /// <param name="uploadBatches"></param>
        private static void DisposeUploadFileStreams(IList<UploadFile[]> uploadBatches)
        {
            foreach (UploadFile[] array in uploadBatches)
            {
                foreach (UploadFile uf in array)
                {
                    if (uf != null && uf.Data != null)
                    {
                        uf.Data.Dispose();
                    }
                }
            }
        }
        
        /// <summary>
        /// Cleanup the image files on disk.
        /// silently fail if unable to delete them.
        /// </summary>
        /// <param name="files"></param>
        private static void DeleteFiles(FileInfo[] files)
        {
            
            foreach (FileInfo fi in files)
            {
                try
                {
                    fi.Delete();
                }
                catch (IOException)
                {
                }
                catch (UnauthorizedAccessException)
                {
                }
                catch (System.Security.SecurityException)
                {
                }
            }
            
        }

        /// <summary>
        /// Build a segmented list of slides so that they can be uploaded in smaller
        /// groups rather than all at the same time.  Each batch will include
        /// a minimum of one slide, but we will stop adding slides to the 
        /// batch if adding the next slide would make the batch size > 400k
        /// </summary>
        /// <param name="files"></param>
        private static IList<UploadFile[]> GroupSlidesIntoBatches(FileInfo[] files)
        {

            IList<UploadFile[]> fileBatches = new List<UploadFile[]>();

            List<UploadFile> currentBatch = null;
            long batchSize = 0;
            for (int i = 0; i < files.Length; i++)
            {
                if (currentBatch == null)
                {
                    currentBatch = new List<UploadFile>();
                }

                // If there is already a slide in the batch and adding the next one
                // would put us over our limit, save the current batch and create a new
                // batch.
                if (currentBatch.Count > 0 &&
                    batchSize + files[i].Length >= NumericConstants.MaxBatchSize)
                {
                    UploadFile[] batchArray = new UploadFile[currentBatch.Count];
                    for (int j = 0; j < currentBatch.Count; j++)
                    {
                        batchArray[j] = currentBatch[j];
                    }
                    fileBatches.Add(batchArray);
                    currentBatch = new List<UploadFile>();
                    batchSize = 0;
                }

                // Add the slide to the current batch and increment the size counter
                currentBatch.Add(new UploadFile(files[i].FullName));
                batchSize += files[i].Length;
            }

            // Add the last batch to our list of batches
            if (currentBatch.Count > 0)
            {
                UploadFile[] batchArray = new UploadFile[currentBatch.Count];
                for (int j = 0; j < currentBatch.Count; j++)
                {
                    batchArray[j] = currentBatch[j];
                }
                fileBatches.Add(batchArray);
            }

            return fileBatches;
        }
        #endregion
    }
}
